深入ES6 (七) Promise

Promise

Promise是异步的一种解决方案,比传统的回调函数更加合理并且强大。

ES6中,Promise是一个对象,这个对象中,有三个状态:1进行中(pending)、2已成功(fulfilled)、3已失败(rejected)。

Promise可以算是一次性容器,Promise这个对象一旦改变状态,该容器即生命周期结束。
也就是说Promise只能发生一次状态改变,只有两种可能:
1、从进行中转为已成功
2、从进行中转为已失败

正是因为这样的特性,导致这Promise有一些局限性,让Promise只能有短暂的生命周期,如果不改变状态,Promise会一直存在,消耗缓存。

1
2
3
4
5
6
7
8
9
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

上面这块代码是Promise的基本用法,resolve和reject是ES6内部的两个函数,我们直接调用就好。

resolve就是从进行中转为成功的状态

reject就是从进行中转为失败的状态

注意!:无论执行resolve还是reject,Promise都会直接结束生命
1
2
3
4
5
promise.then(function(value) {
// success
}, function(error) {
// failure
});

在开发中我们习惯使用then方法来代替resolve和reject麻烦的用法,then方法中接受两个回调,分别代表成功回调和失败回调

1
2
3
4
5
6
7
8
9
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});

上面这块代码是一个Promise实例,实现了触发定时器的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved

上面这块代码证明了Promise对象被创建后就会执行,这也是我所说的,如果要用Promise,请适量而止,并且尽量开门进山的使用异步方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}

这是一个加载图片的实例,当图面加载成功的时候把图片吐出,失败时抛出错误。

1
2
3
4
5
6
7
8
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1

resolve和reject的位置并不会影响js语句的执行。

Promise版Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});

这是一个比较经典的Promise实现的ajax,当返回值为200时抛出成功,并把response吐出。非200时抛出失败。


Promise的方法

1、then()

上文已经提到的then,用于指定成功时的回调函数,接受两个参数(成功、失败)其主要优点在于内部方法中把自己return回来了,所以可以用于链式操作

1
2
3
4
5
6
7
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});

2、catch()

Promise.catch(error)用于指定发生错误时的回调函数,error参数时Promise返回的值

1
2
3
4
5
6
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});

3、all()

Promise.all()用于多个Promise实例全部执行,我们知道Promise因为用完即死的特性,所以如果用到遍历或者多个异步操作时,不可避免创建多个Promise,而all()方法解决了遍历时的痛苦

1
2
3
4
5
6
7
8
9
10
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});

Promise.all([1,2,3])接受一个数组,如果所有Promise都失败时或者有一个成功时才会调用all后的回调函数then。

4、resolve()

有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。

5、reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

1
2
3
4
5
6
7
8
var p = Promise.reject('出错了');
// 等同于
var p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了

6、done()

Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。

1
2
3
4
5
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();

7、finally()

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

1
2
3
4
5
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);